package top.jfunc.common.utils;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.concurrent.Callable;

/**
 * 异常工具类
 * @author 熊诗言
 */
public class ExceptionUtil {
    private ExceptionUtil(){}
    /**
     * 获取异常的堆栈信息。logback自动支持，使用logger.error(msg,Throwable e)
     * @param e 异常
     * @return 异常堆栈
     */
    public static String getExceptionStack(Exception e) {
        StringWriter sw = null;
        PrintWriter pw = null;
        try {
            sw = new StringWriter();
            pw = new PrintWriter(sw);
            // 将出错的栈信息输出到printWriter中
            e.printStackTrace(pw);
            pw.flush();
            sw.flush();
        } finally {
            IoUtil.close(sw);
            IoUtil.close(pw);
        }
        return sw.toString();
    }


    /**
     * 一直往上找，找到根原因
     */
    public static Throwable getRootThrowable(Throwable throwable){
        Throwable t = throwable;
        Throwable temp = throwable;
        while (null != t){
            //记录最后不为空的异常
            temp = t;
            t = t.getCause();
        }
        return temp;
    }


    /**
     * Build a message for the given base message and root cause.
     *
     * @param message the base message
     * @param cause the root cause
     * @return the full exception message
     * @since 3.0
     */
    public static String buildMessage(String message, Throwable cause) {
        if (cause == null) {
            return message;
        }
        StringBuilder sb = new StringBuilder(64);
        if (message != null) {
            sb.append(message).append("; ");
        }
        sb.append("Nested exception is ");
        String nested = cause.getMessage();
        if (nested == null) {
            sb.append(cause);
        }
        else {
            sb.append(buildMessage(cause.getMessage(), cause.getCause()));
        }
        return sb.toString();
    }

    /**
     * Retrieve the innermost cause of the given exception, if any.
     *
     * @param original the original exception to introspect
     * @return the innermost exception, or {@code null} if none
     * @since 3.0
     */
    public static Throwable getRootCause(Throwable original) {
        if (original == null) {
            return null;
        }
        Throwable rootCause = null;
        Throwable cause = original.getCause();
        while (cause != null && cause != rootCause) {
            rootCause = cause;
            cause = cause.getCause();
        }
        return rootCause;
    }

    /**
     * Retrieve the most specific cause of the given exception, that is,
     * either the innermost cause (root cause) or the exception itself.
     * <p>Differs from {@link #getRootCause} in that it falls back
     * to the original exception if there is no root cause.
     *
     * @param original the original exception to introspect
     * @return the most specific cause (never {@code null})
     * @since 3.0
     */
    public static Throwable getMostSpecificCause(Throwable original) {
        Throwable rootCause = getRootCause(original);
        return rootCause != null ? rootCause : original;
    }

    /**
     * Throws any throwable 'sneakily' - you don't need to catch it, nor declare that you throw it onwards.
     * The exception is still thrown - javac will just stop whining about it.
     * <p>
     * Example usage:
     * <pre>
     *   public void run() {
     *     throw sneakyThrow(new IOException("You don't need to catch me!"));
     *   }
     * </pre>
     * <p>
     * NB: The exception is not wrapped, ignored, swallowed, or redefined. The JVM actually does not know or care
     * about the concept of a 'checked exception'. All this method does is hide the act of throwing a checked exception
     * from the java compiler.
     * <p>
     * Note that this method has a return type of {@code RuntimeException}; it is advised you always call this
     * method as argument to the {@code throw} statement to avoid compiler errors regarding no return
     * statement and similar problems. This method won't of course return an actual {@code RuntimeException} -
     * it never returns, it always throws the provided exception.
     *
     * @param t The throwable to throw without requiring you to catch its type.
     * @return A dummy RuntimeException; this method never returns normally, it <em>always</em> throws an exception!
     * @since 4.0
     */
    public static RuntimeException sneakyThrow(Throwable t) {
        if (t == null)
            throw new NullPointerException("t");
        return sneakyThrow0(t);
    }

    @SuppressWarnings("unchecked")
    private static <T extends Throwable> T sneakyThrow0(Throwable t) throws T {
        throw (T) t;
    }

    /**
     * @since 4.0
     */
    public static void sneakyThrow(Action action) {
        try {
            action.call();
        }
        catch (Throwable e) {
            throw sneakyThrow(e);
        }
    }

    public interface Action {
        void call() throws Throwable;
    }

    /**
     * @since 4.0
     */
    public static <T> T sneakyThrow(Callable<T> action) {
        try {
            return action.call();
        }
        catch (Exception e) {
            throw sneakyThrow(e);
        }
    }
}